package com.killer.filter;

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.*;

/**
 * 防ip暴过滤器
 */
@Component
public class BlackIpFilter implements GlobalFilter, Ordered {

    /**
     * 被禁止访问的ip
     */
    private List<String> blackIps=new ArrayList<String>();

    /**
     * 访问统计   key:ip   value:一个定长数组，用来作滑动窗口，数组元素用于保存该下标对应的秒进行了多少次访问
     */
    private Map<String,int[]> visitStaMap=new HashMap<String,int[]>();

    /**
     * 每个ip对应的最近一次的访问时间
     */
    private Map<String, Date> lastVisitTimeMap=new HashMap<String,Date>();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //获取request
        ServerHttpRequest request = exchange.getRequest();
        //获取ip
        String ip = request.getRemoteAddress().getHostString();
        //查看是否在黑名单中
        if (blackIps.contains(ip)) {
            ServerHttpResponse response = exchange.getResponse();
            // 决绝访问，返回
            response.setStatusCode(HttpStatus.SEE_OTHER); // 状态码
            DataBuffer wrap = response.bufferFactory().wrap("该ip频繁进行注册，已被禁止访问".getBytes());
            return response.writeWith(Mono.just(wrap));
        }
        //获取url
        String url=request.getPath().toString();
        //查看是否是注册接口
        if (url.startsWith("/user/register")) {
            //记录该ip访问
            recordIpVisit(ip);
        }
        //放行
        return chain.filter(exchange);
    }

    private void recordIpVisit(String ip) {
        int[] visitRecords = visitStaMap.get(ip);
        //如果为空，创建一个长度为60的数组代表每一秒
        if (visitRecords == null) {
            visitRecords = new int[60];
            for (int visitRecord : visitRecords) {
                visitRecord=0;
            }
        }
        //当前时间
        Date now=new Date();
        //获取当前秒数
        int second = getTimeSecond(now);
        //在对应数组下标加1
        visitRecords[second]=visitRecords[second]+1;
        //获取上次访问时间
        Date lastTime = lastVisitTimeMap.get(ip);
        if (lastTime != null) {
            //获取上次秒数
            int lastSecond = getTimeSecond(lastTime);
            //对比上次时间
            if (now.getTime() - lastTime.getTime() >= 60000) {
                //如果大于1分钟，则证明这一分钟内只有该秒有请求，除了该下标，其余全部置0
                for (int i = 0; i < visitRecords.length; i++) {
                    if (i != second) {
                        visitRecords[i]=0;
                    }
                }
            }else{
                //不大于一分钟，代表上次秒数到这里的秒数之间没有请求，这里下标的数据要置0
                if (second >= lastSecond) {
                    //如果当前秒数大于上次秒数，中间的置0
                    for (int i = lastSecond+1; i < second; i++) {
                        visitRecords[i]=0;
                    }
                }else{
                    //否则，头尾置0
                    for (int i = lastSecond + 1; i < 60; i++) {
                        visitRecords[i]=0;
                    }
                    for (int i = 0; i < second; i++) {
                        visitRecords[i]=0;
                    }
                }
            }
        }
        visitStaMap.put(ip, visitRecords);
        //覆盖最近一次访问时间
        lastVisitTimeMap.put(ip, now);
        //统计是否需要记录到黑名单
        int totalVist=0;
        for (int visitRecord : visitRecords) {
            totalVist=totalVist+visitRecord;
        }
        if (totalVist >= 100) {
            blackIps.add(ip);
        }
    }

    /**
     * 获取当前时间秒数
     * @param time
     * @return
     */
    private int getTimeSecond(Date time) {
        Calendar calendar=Calendar.getInstance();
        calendar.setTime(time);
        return calendar.get(Calendar.SECOND);
    }

    @Override
    public int getOrder() {
        return 5;
    }
}
